import pandas as pd
import numpy as np
from collections import Counter


# --- 1. 数据加载 ---
# 假设您的Excel文件名为 'fucai_3d_history.xlsx'，并且数据在第一个sheet中
# 请确保文件路径正确
def load_data(filepath='fucai_3d_history.xlsx'):
    """
    从Excel文件加载福彩3D历史数据。
    假设列名为 'issue', 'hundred', 'ten', 'one'。
    数据按时间从远到近排列（最新一期在最下面）。
    """
    try:
        df = pd.read_excel(filepath)
        print(f"成功加载数据，共 {len(df)} 期。")
        # 确保列名正确，可根据实际情况调整
        df.rename(columns={'期号': 'issue', '百位': 'hundred', '十位': 'ten', '个位': 'one'}, inplace=True)
        # 确保数据类型为整数
        df[['hundred', 'ten', 'one']] = df[['hundred', 'ten', 'one']].astype(int)
        return df
    except FileNotFoundError:
        print(f"错误：找不到文件 {filepath}。请检查文件路径和名称。")
        return pd.DataFrame()  # 返回空DataFrame
    except Exception as e:
        print(f"加载数据时发生错误: {e}")
        return pd.DataFrame()


# --- 2. 规则定义 ---
# 杀胆规则 (返回应被排除的号码集合)
def kill_rule_1_adjacent_repeat(last_digit):
    """邻期重复杀胆"""
    return {last_digit}


def kill_rule_2_adjacent_neighbor(last_digit):
    """邻期邻号杀胆"""
    return {(last_digit - 1) % 10, last_digit, (last_digit + 1) % 10}


def kill_rule_3_high_omission(df, position, window=20, threshold_multiplier=2):
    """遗漏值杀胆"""
    if len(df) < window:
        return set()
    recent_data = df.tail(window)[position]
    counts = Counter(recent_data)
    total_draws = len(recent_data)
    expected_freq = total_draws / 10.0
    current_omissions = {}
    # 计算当前遗漏 (从最后一期往前数)
    for num in range(10):
        # [::-1] 翻转序列以便从最近的开始找
        try:
            omission = list(recent_data[::-1]).index(num)
        except ValueError:
            omission = len(recent_data)  # 如果窗口期内没出现，则遗漏为窗口大小
        current_omissions[num] = omission

    # 杀掉遗漏值远高于平均的号码
    to_kill = {num for num, om in current_omissions.items() if om > expected_freq * threshold_multiplier}
    return to_kill


def kill_rule_4_cold_number(df, position, window=20):
    """冷号杀胆"""
    if len(df) < window:
        return set()
    recent_data = df.tail(window)[position]
    counts = Counter(recent_data)
    # 找到出现次数最少的号码
    min_count = min(counts.values())
    cold_numbers = {num for num, count in counts.items() if count == min_count}
    return cold_numbers


# 定胆规则 (返回应被保留的号码集合)
def fix_rule_1_hot_number(df, position, window=10):
    """热号定胆"""
    if len(df) < window:
        return set(range(10))
    recent_data = df.tail(window)[position]
    counts = Counter(recent_data)
    # 找到出现次数最多的号码
    max_count = max(counts.values())
    hot_numbers = {num for num, count in counts.items() if count == max_count}
    return hot_numbers


def fix_rule_2_low_omission(df, position, window=20):
    """遗漏值定胆 (保留近期出现过的)"""
    if len(df) < 1:
        return set()
    # 简单处理：保留上一期出现的号码
    last_digit = df.iloc[-1][position]
    return {last_digit}


def fix_rule_3_adjacent_neighbor(last_digit):
    """邻期邻号定胆"""
    return {(last_digit - 1) % 10, (last_digit + 1) % 10}


# --- 3. 回测框架 ---
def backtest_single_rule(df, position, rule_func, rule_type='kill', *args, **kwargs):
    """
    对单个规则进行历史回测。
    rule_type: 'kill' 或 'fix'
    """
    correct_predictions = 0
    total_predictions = 0  # 有效预测次数

    # 从第二期开始，用前一期的数据预测当期
    for i in range(1, len(df)):
        current_actual = df.iloc[i][position]
        previous_data = df.iloc[:i]  # 包含到上一期为止的所有数据

        # 根据规则类型调用不同的规则函数
        if rule_type == 'kill':
            # 杀胆规则：预测不包含当前实际号码则正确
            predicted_to_kill = rule_func(previous_data, position, *args, **kwargs) if 'df' in rule_func.__code__.co_varnames else rule_func(df.iloc[i - 1][position], *args, **kwargs)
            # 杀胆正确的条件：实际号码在被杀号码中
            if current_actual in predicted_to_kill:
                correct_predictions += 1
            total_predictions += 1

        elif rule_type == 'fix':
            # 定胆规则：预测包含当前实际号码则正确
            predicted_to_fix = rule_func(previous_data, position, *args, **kwargs) if 'df' in rule_func.__code__.co_varnames else rule_func(df.iloc[i - 1][position], *args, **kwargs)
            # 定胆正确的条件：实际号码在被定号码中
            if current_actual in predicted_to_fix:
                correct_predictions += 1
            # 只有当预测集合不为空时才算一次有效预测
            if predicted_to_fix:
                total_predictions += 1

    if total_predictions == 0:
        accuracy = 0.0
    else:
        accuracy = correct_predictions / total_predictions
    return accuracy, correct_predictions, total_predictions


# --- 4. 主程序入口 ---
if __name__ == "__main__":
    # 加载数据
    data_file = '3d.xlsx'  # 请替换为您的实际文件名
    df_history = load_data(data_file)

    if df_history.empty:
        print("未能加载历史数据，程序退出。")
    else:
        # --- 示例：对部分规则进行回测 ---
        print("\n--- 开始回测 ---")
        position_to_test = 'hundred'  # 可以改为 'ten' 或 'one'

        print(f"\n正在回测 {position_to_test} 位规则...")

        # 回测杀胆规则
        print("\n--- 杀胆规则回测结果 ---")
        rules_to_test_kill = [
            # (kill_rule_1_adjacent_repeat, "邻期重复杀胆", df_history.iloc[-2][position_to_test]), # 需要上一期数据
            (kill_rule_2_adjacent_neighbor, "邻期邻号杀胆", df_history.iloc[-2][position_to_test]),
            (kill_rule_3_high_omission, "遗漏值杀胆", 20, 2),  # window=20, multiplier=2
            (kill_rule_4_cold_number, "冷号杀胆", 20),  # window=20
        ]

        kill_results = []
        for rule_func, rule_name, *rule_args in rules_to_test_kill:
            # 特殊处理邻期规则，需要动态传入上一期号码
            if rule_func in [kill_rule_1_adjacent_repeat, kill_rule_2_adjacent_neighbor]:
                # 对于需要上一期号码的规则，在回测函数内部处理
                # 这里我们简化，直接用回测函数的逻辑
                acc, correct, total = backtest_single_rule(df_history, position_to_test, rule_func, 'kill')
            else:
                # 对于需要历史数据的规则
                acc, correct, total = backtest_single_rule(df_history, position_to_test, rule_func, 'kill', *rule_args)

            kill_results.append((rule_name, acc, correct, total))
            print(f"规则: {rule_name:<15} | 准确率: {acc:.4f} ({correct}/{total})")

        # 回测定胆规则
        print("\n--- 定胆规则回测结果 ---")
        rules_to_test_fix = [
            (fix_rule_1_hot_number, "热号定胆", 10),  # window=10
            (fix_rule_2_low_omission, "遗漏值定胆", 20),  # window=20
            (fix_rule_3_adjacent_neighbor, "邻期邻号定胆", df_history.iloc[-2][position_to_test]),
        ]

        fix_results = []
        for rule_func, rule_name, *rule_args in rules_to_test_fix:
            if rule_func in [fix_rule_3_adjacent_neighbor]:
                acc, correct, total = backtest_single_rule(df_history, position_to_test, rule_func, 'fix')
            else:
                acc, correct, total = backtest_single_rule(df_history, position_to_test, rule_func, 'fix', *rule_args)

            fix_results.append((rule_name, acc, correct, total))
            print(f"规则: {rule_name:<15} | 准确率: {acc:.4f} ({correct}/{total})")

        # --- 5. 规则排序 (示例) ---
        print("\n--- 规则排序 (按准确率) ---")
        kill_results.sort(key=lambda x: x[1], reverse=True)  # 按准确率降序
        print(f"\n{position_to_test}位 杀胆规则优先级 (高->低):")
        for name, acc, _, _ in kill_results:
            print(f"  {name} (准确率: {acc:.4f})")

        fix_results.sort(key=lambda x: x[1], reverse=True)  # 按准确率降序
        print(f"\n{position_to_test}位 定胆规则优先级 (高->低):")
        for name, acc, _, _ in fix_results:
            print(f"  {name} (准确率: {acc:.4f})")

        # --- 6. 简单推荐示例 ---
        print("\n--- 简单推荐示例 (基于最新一期数据) ---")
        if len(df_history) >= 2:
            last_issue_data = df_history.iloc[-1]  # 最新一期
            second_last_issue_data = df_history.iloc[-2]  # 倒数第二期

            print(f"最新一期 ({last_issue_data['issue']}) 开奖号码: {last_issue_data['hundred']}{last_issue_data['ten']}{last_issue_data['one']}")
            print(f"倒数第二期 ({second_last_issue_data['issue']}) 开奖号码: {second_last_issue_data['hundred']}{second_last_issue_data['ten']}{second_last_issue_data['one']}")

            # 示例：百位杀3个号 (使用准确率最高的3个杀胆规则)
            print("\n示例：为百位杀3个号")
            top_3_kill_rules = kill_results[:3]
            all_killed = set()
            for rule_name, _, _, _ in top_3_kill_rules:
                print(f"  应用规则: {rule_name}")
                # 这里需要根据规则名调用实际函数，为简化，我们手动调用
                if "邻期邻号" in rule_name:
                    killed = kill_rule_2_adjacent_neighbor(second_last_issue_data['hundred'])
                elif "遗漏值" in rule_name and "杀胆" in rule_name:
                    killed = kill_rule_3_high_omission(df_history, 'hundred', 20, 2)
                elif "冷号" in rule_name and "杀胆" in rule_name:
                    killed = kill_rule_4_cold_number(df_history, 'hundred', 20)
                else:
                    killed = set()  # 默认不杀
                print(f"    杀号结果: {sorted(killed)}")
                all_killed.update(killed)

            final_killed = list(all_killed)[:3]  # 简化，取并集后取前3
            remaining = sorted(list(set(range(10)) - set(final_killed)))
            print(f"  综合杀号 (取前3): {sorted(final_killed)}")
            print(f"  百位推荐 (剩余): {remaining}")

            # 示例：个位定2个号 (使用准确率最高的2个定胆规则)
            print("\n示例：为个位定2个号")
            top_2_fix_rules = fix_results[:2]
            all_fixed = set()
            for rule_name, _, _, _ in top_2_fix_rules:
                print(f"  应用规则: {rule_name}")
                # 手动调用
                if "热号" in rule_name and "定胆" in rule_name:
                    fixed = fix_rule_1_hot_number(df_history, 'one', 10)
                elif "遗漏值" in rule_name and "定胆" in rule_name:
                    fixed = fix_rule_2_low_omission(df_history, 'one', 20)
                elif "邻期邻号" in rule_name and "定胆" in rule_name:
                    fixed = fix_rule_3_adjacent_neighbor(second_last_issue_data['one'])
                else:
                    fixed = set(range(10))  # 默认全定
                print(f"    定号结果: {sorted(fixed)}")
                all_fixed.update(fixed)

            final_fixed = list(all_fixed)[:2]  # 简化，取并集后取前2
            print(f"  综合定号 (取前2): {sorted(final_fixed)}")
